
C++中查找名称有许多小技巧，但这里只关注几个主要的。只有在下面两种情景中才有必要确认名称查找的细节：(1)直接处理会犯错的普通例子(2)C++标准给出的错误例子。

限定名在限定构造所隐含的范围内查找。若该作用域是一个类，那么也可以搜索基类。但查找限定名称时，不考虑封闭作用域。基本原则如下:

\begin{lstlisting}[style=styleCXX]
int x;

class B {
	public:
	int i;
};

class D : public B {
};

void f(D* pd)
{
	pd->i = 3; // finds B::i
	D::x = 2; // ERROR: does not find ::x in the enclosing scope
}
\end{lstlisting}

相比之下，不限定名通常会依次在外围作用域中查找(尽管在成员函数定义中，类及其基类的作用域会在其他外围作用域之前搜索)，这称为常规查找。下面是一个基本的例子，展示了常规查找的主要思想:

\begin{lstlisting}[style=styleCXX]
extern int count; // #1

int lookup_example(int count) // #2
{
	if (count < 0) {
		int count = 1; // #3
		lookup_example(count); // unqualified count refers to #3
	}
	return count + ::count; // the first (unqualified) count refers to #2 ;
} // the second (qualified) count refers to #1
\end{lstlisting}

对于非限定名称的查找，最近增加了一项新的查找机制——除了常规的查找之外——有时可能要进行依赖参数的查找(ADL)。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}在C++98/C++03中，这也称为Koenig查找(或扩展Koenig查找)，以Andrew Koenig命名，他首先提出了这种机制的变体。
\end{tcolorbox}

继续研究ADL之前，用max()模板来触发这个机制:

\begin{lstlisting}[style=styleCXX]
template<typename T>
T max (T a, T b)
{
	return b < a ? a : b;
}
\end{lstlisting}

假设现在需要将此模板应用于，定义在另一个命名空间中的类型:

\begin{lstlisting}[style=styleCXX]
namespace BigMath {
	class BigNumber {
		...
	};
	bool operator < (BigNumber const&, BigNumber const&);
	...
}

using BigMath::BigNumber;

void g (BigNumber const& a, BigNumber const& b)
{
	...
	BigNumber x = ::max(a,b);
	...
}
\end{lstlisting}

这里的问题是max()模板不知道BigMath名称空间，但是常规查找无法找到适用于BigNumber类型值的小于操作符。如果没有一些特殊的规则，这将大大降低模板在C++名称空间中的适用性。ADL就是对这些“特殊规则”的回答。

\subsubsubsection{13.2.1\hspace{0.2cm}ADL}


ADL主要适用于非限定名称，这些名称看起来像是在函数调用或操作符调用中命名非成员函数。若在常规查找中，就不会发生ADL了

\begin{itemize}
\item 
成员函数的名称，

\item 
变量名，

\item 
类型的名称，或

\item 
块作用域函数声明的名称。
\end{itemize}

若要调用的函数名在括号内，则ADL会受到抑制。

否则，若名称后面跟着用圆括号括起来的参数表达式列表，ADL将继续在与调用参数类型“相关”的命名空间和类中查找名称。这些关联的命名空间和关联类的精确定义将在后面给出，但它们可以认为是将给定类型，直接相连的所有命名空间和类。如果类型是指向类X的指针，那么相关的类和命名空间将包括X，以及X所属的命名空间或类。

对于给定类型的关联命名空间和关联类的精确定义，由以下规则确定:

\begin{itemize}
\item 
内置类型，这是空集。

\item 
指针和数组类型，关联的命名空间和类的集合是基础类型的集合。

\item 
枚举类型，关联的命名空间是在其中声明枚举的命名空间。

\item 
类成员，外围类是关联类。

\item 
类类型(包括联合类型)，相关类的集合是类型本身、封闭类，以及直接和间接基类。关联命名空间的集合在其中声明关联类的命名空间。如果类是类模板实例，则模板类型参数的类型，以及声明模板模板参数的类和命名空间也包括在内。

\item 
函数类型，关联的命名空间和类集合包含，与所有参数类型和返回类型关联的命名空间和类。

\item 
指向类X成员类型的指针，关联的命名空间和类集除了与成员类型关联的命名空间和类外，还包括与X关联的命名空间和类(如果是指向成员函数类型的指针，那么参数和返回类型也有作用)。
\end{itemize}

然后，ADL在所有关联的命名空间中查找该名称，就好像该名称已经通过这些命名空间进行了限定一样(除非忽略using的指示):

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/adl.cpp}
\begin{lstlisting}[style=styleCXX]
#include <iostream>

namespace X {
	template<typename T> void f(T);
}

namespace N {
	using namespace X;
	enum E { e1 };
	void f(E) {
		std::cout << "N::f(N::E) called\n";
	}
}

void f(int)
{
	std::cout << "::f(int) called\n";
}

int main()
{
	::f(N::e1); // qualified function name: no ADL
	f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(),
} // the latter is preferred
\end{lstlisting}

本例中执行ADL时，命名空间N中的using指令会忽略。因此，X::f()不是main()中函数调用的候选。

\subsubsubsection{13.2.2\hspace{0.2cm}友元声明的ADL}

友元函数声明可以是指定函数，假定函数是在包含友元声明的类的最近的命名空间作用域(可能是全局作用域)中声明的。但是，这样的友元声明在该作用域中不可见。

\begin{lstlisting}[style=styleCXX]
template<typename T>
class C {
	...
	friend void f();
	friend void f(C<T> const&);
	...
};

void g (C<int>* p)
{
	f(); // is f() visible here?
	f(*p); // is f(C<int> const&) visible here?
}
\end{lstlisting}

如果友元声明在封闭的命名空间中可见，实例化类模板可能会使普通函数的声明可见。这将导致令人惊讶的行为:调用f()将导致编译错误，除非在程序的早些时候实例化了类C!

另一方面，只在友元声明中声明(和定义)函数也可以(参见21.2.1节，了解这种行为的方式)。当是友元的类在ADL的关联类中时，就可以找到这样的函数了。

重新在看下上一个例子。调用f()没有相关类或命名空间，因为没有参数，所以是无效的调用。但调用f(*p)确实有关联类C<int>(因为类型是*p)，并且全局命名空间也相关联(因为这是声明*p类型的命名空间)。因此，若调用之前完全实例化C<int>，就可以找到第二个友元函数声明。为了确保这一点，假设涉及查找关联类中的友元的调用会导致类的实例化(若没有这样做的话)。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}虽然这是C++标准作者们的意图，但在标准中并没有明确说明。
\end{tcolorbox}

ADL查找友元声明和定义的能力，有时称为友元名称注入。然而，这个术语有一定的误导性，因为它是C++标准之前的一个特性名称，该特性“注入”了友元声明的名称到外围作用域，使它们对普通的名称查找可见。所以在上面的例子中，两个调用是定义良好的。本章的后记会进一步详细介绍友元注入的历史。

\subsubsubsection{13.2.3\hspace{0.2cm}注入类名}

类名注入到该类本身的作用域中，可以作为该作用域中的非限定名称访问(只是用来表示构造函数的符号，所以不能作为限定名访问):

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/adl.cpp}
\begin{lstlisting}[style=styleCXX]
#include <iostream>

int C;

class C {
private:
	int i[2];
public:
	static int f() {
		return sizeof(C);
	}
};

int f()
{
	return sizeof(C);
}

int main()
{
	std::cout << "C::f() = " << C::f() << ’,’
	<< " ::f() = " << ::f() << ’\n’;
}
\end{lstlisting}

成员函数C::f()返回类型C的大小，而函数::f()返回变量C的大小(int的大小)。

类模板也有注入类名，但比普通的方式更奇怪:它们后面可以跟着模板参数(这种情况下，注入的是类模板名)，但是如果后面没有模板参数，就表示将其形参作为实参的类(对于偏特化，则表示其特化的实参)，如果是上下文需要类型，则表示为模板。这就解释了以下情况:

\begin{lstlisting}[style=styleCXX]
template<template<typename> class TT> class X {
};

template<typename T> class C {
	C* a; // OK: same as “C<T>* a;”
	C<void>& b; // OK
	X<C> c; // OK: C without a template argument list denotes the template C
	X<::C> d; // OK: ::C is not the injected class name and therefore always
			  // denotes the template
};
\end{lstlisting}

注意非限定名称是如何引用注入名称的，如果没有模板参数，就不会认为是模板名称。为了弥补这一点，可以使用作用域限定符::强制找到模板名称。

可变参数模板注入的类名还有一个问题:若通过使用可变参数模板的模板参数直接注入类名，那么注入的类名将包含没有展开的模板参数包(参见12.4.1节了解包展开的详细信息)。因此，当为可变参数模板参数注入类名时，对应的模板参数包是一个模式为该模板参数包的包展开:

\begin{lstlisting}[style=styleCXX]
template<int I, typename... T> class V {
	V* a; // OK: same as “V<I, T...>* a;”
	V<0, void> b; // OK
};
\end{lstlisting}

\subsubsubsection{13.2.4\hspace{0.2cm}当前实例化类}

为类或类模板的注入类名实际上是定义类型的别名。对于非模板类，这个属性很明显，因为类本身是具有该名称，且是在该作用域中的类型。但在类模板或类模板内嵌套的类中，每个模板实例都是不同的类型。这个属性在上下文中特别有趣，因为它注入的类名引用了类模板的相同实例化类，而不是该类模板的其他特化(对于类模板的嵌套类也是如此)。

类模板中，注入类名或任何与外围类，或类模板注入类名等价的类型(包括查看类型别名声明)都指向当前的实例。依赖于模板参数的类型(即依赖类型)，但不引用当前实例化类型则称为引用未知特化，该特化可以从相同的类模板或完全不同的类模板实例化。下面的例子说明了其中的区别:

\begin{lstlisting}[style=styleCXX]
template<typename T> class Node {
	using Type = T;
	Node* next; // Node refers to a current instantiation
	Node<Type>* previous; // Node<Type> refers to a current instantiation
	Node<T*>* parent; // Node<T*> refers to an unknown specialization
};
\end{lstlisting}

嵌套类和类模板的情况下，确定类型是否引用当前实例化可能会造成混淆。外围类和类模板(或与其等价的类型)的注入类名引用当前实例化类，而其他嵌套类或类模板的名称则不引用:

\begin{lstlisting}[style=styleCXX]
template<typename T> class C {
	using Type = T;
	struct I {
		C* c; // C refers to a current instantiation
		C<Type>* c2; // C<Type> refers to a current instantiation
		I* i; // I refers to a current instantiation
	};

	struct J {
		C* c; // C refers to a current instantiation
		C<Type>* c2; // C<Type> refers to a current instantiation
		I* i; // I refers to an unknown specialization,
			  // because I does not enclose J
		J* j; // J refers to a current instantiation
	};
};
\end{lstlisting}

当类型引用当前实例化时，其内容保证从当前定义的类模板或其嵌套类中实例化。这对解析模板时的名称查找有影响，但也产生了另一种更类似游戏的方法，以确定类模板定义中的类型X是引用当前的实例化，还是未知的特化:若另一个开发者编写显式特化(第16章中详细描述)，使X引用该特化，那么X引用未知的特化。上面的例子中考虑类型C<int>::J的实例化:用于实例化C<T>::J的定义(因为这是正在实例化的类型)。此外，由于显式特化需要在了解所有外围模板或成员的情况下特化模板或模板成员，因此C<int>将从外围类定义实例化。所以，对J和C<int>(其中Type为int)的引用指向当前的实例化类。另外，可以为C<int>::I编写显式特化类:

\begin{lstlisting}[style=styleCXX]
template<> struct C<int>::I {
	// definition of the specialization
};
\end{lstlisting}

这里，为C<int>::I的特化提供了一个与C<T>::J定义中可见的定义完全不同的定义。因此C<T>::J定义中，I指的是一个未知的特化类。


